/*
qnettest - Qt4 application for testing network performance
Copyright (C) 2007  Pyry Haulos

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include <QtCore>
#include <QtNetwork>
#include "tcpproto.hpp"

TcpProto::TcpProto(QTcpSocket * socket, TcpProtoTestData * data)
{
	this->socket = socket;
	testdata = data;
	packet_num = 0;
}

TcpProto::~TcpProto()
{
	// cleanup?
}

TcpProtoTestData *TcpProto::generateTestData(qint32 size)
{
	TcpProtoTestData *data = new TcpProtoTestData;

#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::generateTestData():" << QObject::tr("generating %1 bytes of test data").arg(size);
#endif

	data->data = new char[size];
	data->size = size;
	data->mutex = new QMutex;

	return data;
}

void TcpProto::freeTestData(TcpProtoTestData * data)
{
	// this function is not thread safe!

	delete[]data->data;
	data->data = NULL;
	delete data->mutex;
	data->mutex = NULL;
	delete data;
}

void TcpProto::resizeTestData(TcpProtoTestData * data, qint32 size)
{
	data->mutex->lock();

	if (data->size == size) {
		data->mutex->unlock();
		return;
	}
#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::resizeTestData():" << QObject::tr("resizing test data to size %1").arg(size);
#endif

	delete[]data->data;
	data->data = new char[size];
	data->size = size;

	data->mutex->unlock();
}

bool TcpProto::send(qint32 send_size, qint32 recv_size)
{
#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::send():" << QObject::tr("sending %1 bytes, requesting %2 bytes").arg(send_size).arg(recv_size);
#endif

	socket->write((char *) &send_size, sizeof(qint32));
	socket->write((char *) &recv_size, sizeof(qint32));

	if (!socket->flush()) {
		showLastError();
		return false;
	}

	send_size -= sendHeaderSize();

	if (testdata->size < send_size)
		resizeTestData(testdata, send_size);

	if (testdata->size < recv_size)
		resizeTestData(testdata, recv_size);

	if (socket->write(testdata->data, send_size) != send_size) {
		showLastError();
		showError(QObject::tr("couldn't write exactly %1 bytes").arg(send_size));
		return false;
	}

	if (!socket->flush()) {
		showLastError();
		return false;
	}
#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::send():" << QObject::tr("sent %1 bytes").arg(send_size + sendHeaderSize());
#endif

	if (!waitForBytes(recvHeaderSize()))
		return false;

	qint32 got_recv_size = 0;
	socket->read((char *) &got_recv_size, sizeof(qint32));

	got_recv_size -= recvHeaderSize();
	recv_size -= recvHeaderSize();

	if (got_recv_size != recv_size) {
		showLastError();
		showError(QObject::tr("response size (%1) does not match requested size (%2)").arg(got_recv_size).arg(recv_size));
	}

	if (!skipData(recv_size))
		return false;

#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::send():" << QObject::tr("recieved %1 bytes").arg(recv_size + recvHeaderSize());
#endif

#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::send():" << QObject::tr("recieved packet %1").arg(packet_num);
#endif

	packet_num++;

	return true;
}

bool TcpProto::reply()
{
	qint32 send_size = -1;
	qint32 recv_size = -1;

	if (!waitForBytes(sendHeaderSize()))
		return false;

	socket->read((char *) &send_size, sizeof(qint32));
	socket->read((char *) &recv_size, sizeof(qint32));

#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::reply():" << QObject::tr("client sent %1 bytes, requesting %2 bytes").arg(send_size).arg(recv_size);
#endif

	send_size -= sendHeaderSize();

	if (send_size < 0 | recv_size < 0) {
		showLastError();
		showError(QObject::tr("number of sent bytes (%1) or number of requested bytes (%2) is negative").arg(send_size).arg(recv_size));
		return false;
	}

	if (!skipData(send_size))
		return false;

#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::reply():" << QObject::tr("recieved %1 bytes").arg(send_size + sendHeaderSize());
#endif

	socket->write((char *) &recv_size, sizeof(qint32));

	if (!socket->flush()) {
		showLastError();
		return false;
	}

	recv_size -= recvHeaderSize();

	if (testdata->size < recv_size)
		resizeTestData(testdata, recv_size);

	if (socket->write(testdata->data, recv_size) != recv_size) {
		showLastError();
		showError(QObject::tr("couldn't write exactly %1 bytes").arg(recv_size));
		return false;
	}

	if (!socket->flush()) {
		showLastError();
		return false;
	}
#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::reply():" << QObject::tr("sent %1 bytes").arg(recv_size + recvHeaderSize());
#endif

#ifdef TCPPROTO_DEBUG
	qDebug() << "TcpProto::reply():" << QObject::tr("sent packet %1").arg(packet_num);
#endif

	packet_num++;

	return true;
}

void TcpProto::showLastError()
{
	if (socket->error() < 0)
		return;

	showError(socket->errorString());
}

void TcpProto::showError(QString error)
{
	qWarning() << "TcpProto::error():" << error;
}

bool TcpProto::skipData(qint32 size)
{
	if (testdata->size < size)
		resizeTestData(testdata, size);

	waitForBytes(size);
	socket->read(testdata->data, size);

	return true;
}

bool TcpProto::waitForBytes(qint32 size)
{
	while (socket->bytesAvailable() < size) {
		if (!socket->waitForReadyRead()) {
			showLastError();
			return false;
		}

		if (socket->error() >= 0) {
			showLastError();
			return false;
		} else if (socket->state() != QTcpSocket::ConnectedState)
			return false;
	}

	return true;
}
